package onlinebanking.server;

import onlinebanking.constants.*;
import onlinebanking.network.*;
import onlinebanking.server.gui.AdministratorFrame;
import onlinebanking.sql.*;

import java.io.IOException;
import java.net.*;
import java.sql.Connection;
import java.util.HashMap;
import java.util.Observable;

/**
 * Bank Server Singleton Design
 * 
 * @author Del Mundo
 * 
 */
public class SimpleServer extends Observable {

	/**
	 * ServerSocket holder
	 */
	private volatile static SimpleServer serverInstance = null;

	/**
	 * Server Socket
	 */
	private ServerSocket serversocket;

	/**
	 * List of clients hashed according to their IP addresses
	 */
	private HashMap<String, ServerEngine> clientlist;

	/**
	 * Connection to the database driver
	 */
	private SQLConnection sqlconnection;

	private DataObject serverDataObject;

	/**
	 * Default constructor. Initalizes the server socket and prepares the
	 * SimpleServer to accept incoming connections.
	 * 
	 * @throws IOException
	 */
	private SimpleServer() throws IOException {

		this.serversocket = new ServerSocket(Network.SERVER_PORT);
		this.sqlconnection = SQLConnection.getInstance();
		this.clientlist = new HashMap<String, ServerEngine>();

		this.serverDataObject = new DataObject(this);

		System.out.println("Server: Starting...");
		this.updateDataObject("Server: Starting...", null, null);
		
		while (true) {
			System.out.println("Server: Waiting for client...");
			this.updateDataObject("Server: Waiting for client...", null, null);			
			Socket s = serversocket.accept();
			ServerEngine clientHandler = new ServerEngine(s);
			String clientIP = s.getInetAddress() + "";

			// check if duplicate connections
			if (clientlist.get(clientIP) == null) {

				clientHandler.setClientIP(clientIP);

				// place clienthandler in hash with ip address as key
				clientlist.put(clientIP, clientHandler);
				clientHandler.setServer(this);
				clientHandler.start();
				System.out.println("SimpleServer: Client connected IP = "
						+ clientIP);
				this.updateDataObject("SimpleServer: Client connected IP = "
						+ clientIP, null, null);
			} else {
				System.out.println("SimpleServer: DUPLICATE CONNECTION IP = "
						+ clientIP);
				this.updateDataObject("SimpleServer: DUPLICATE CONNECTION IP = "
						+ clientIP, null, null);				
				Message msg = new Message();
				msg.setMsgType(MSG.CONNECTION_REFUSED);
				msg.addAttribute("reason", "DUPLICATE CONNECTION IP = "
						+ clientIP);
				try {
					clientHandler.sendMessage(msg);
				} catch (Exception e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}

		}
	}

	private SimpleServer(AdministratorFrame admin) throws IOException {
		
		this.addObserver(admin);
		
		this.serversocket = new ServerSocket(Network.SERVER_PORT);
		this.sqlconnection = SQLConnection.getInstance();
		this.clientlist = new HashMap<String, ServerEngine>();

		this.serverDataObject = new DataObject(this);

		System.out.println("Server: Starting...");
		this.updateDataObject("Server: Starting...", null, null);
		new Thread(new Runnable() {
			public void run() {
				while (true) {
					System.out.println("Server: Waiting for client...");
					SimpleServer.this.updateDataObject("Server: Waiting for client...", null, null);			
					Socket s;
					try {
						s = serversocket.accept();
					} catch (IOException e1) {
						// TODO Auto-generated catch block
						e1.printStackTrace();
						break;
					}
					
					ServerEngine clientHandler = new ServerEngine(s);
					String clientIP = s.getInetAddress() + "";

					// check if duplicate connections
					if (clientlist.get(clientIP) == null) {

						clientHandler.setClientIP(clientIP);

						// place clienthandler in hash with ip address as key
						clientlist.put(clientIP, clientHandler);
						clientHandler.start();
						System.out.println("SimpleServer: Client connected IP = "
								+ clientIP);
						SimpleServer.this.updateDataObject("SimpleServer: Client connected IP = "
								+ clientIP, null, null);
					} else {
						System.out.println("SimpleServer: DUPLICATE CONNECTION IP = "
								+ clientIP);
						SimpleServer.this.updateDataObject("SimpleServer: DUPLICATE CONNECTION IP = "
								+ clientIP, null, null);				
						Message msg = new Message();
						msg.setMsgType(MSG.CONNECTION_REFUSED);
						try {
							clientHandler.sendMessage(msg);
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}

			}
			}
		}).start();
		
//		while (true) {
//			System.out.println("Server: Waiting for client...");
//			this.updateDataObject("Server: Waiting for client...", null, null);			
//			Socket s = serversocket.accept();
//			ServerEngine clientHandler = new ServerEngine(s);
//			String clientIP = s.getInetAddress() + "";
//
//			// check if duplicate connections
//			if (clientlist.get(clientIP) == null) {
//
//				clientHandler.setClientIP(clientIP);
//
//				// place clienthandler in hash with ip address as key
//				clientlist.put(clientIP, clientHandler);
//				clientHandler.start();
//				System.out.println("SimpleServer: Client connected IP = "
//						+ clientIP);
//				this.updateDataObject("SimpleServer: Client connected IP = "
//						+ clientIP, null, null);
//			} else {
//				System.out.println("SimpleServer: DUPLICATE CONNECTION IP = "
//						+ clientIP);
//				this.updateDataObject("SimpleServer: DUPLICATE CONNECTION IP = "
//						+ clientIP, null, null);				
//				Message msg = new Message();
//				msg.setMsgType(MSG.CONNECTION_REFUSED);
//				try {
//					clientHandler.sendMessage(msg);
//				} catch (Exception e) {
//					// TODO Auto-generated catch block
//					e.printStackTrace();
//				}
//			}

		}
	
	
	/**
	 * Gets the instance of the Bank Server
	 * 
	 * @return the instance of the Bank Server
	 * @throws IOException
	 */
	public static SimpleServer getInstance() throws IOException {
		if (serverInstance == null) { // double checking
			synchronized (SimpleServer.class) {
				if (serverInstance == null) {
					SimpleServer.serverInstance = new SimpleServer();
				}
			}
		}
		return serverInstance;
	}
	
	/**
	 * Gets the instance of the Bank Server
	 * 
	 * @param admin The UI of the server.
	 * @return the instance of the Bank Server
	 * @throws IOException
	 */
	public static SimpleServer getInstance(AdministratorFrame admin) throws IOException {
		if (serverInstance == null) { // double checking
			synchronized (SimpleServer.class) {
				if (serverInstance == null) {
					SimpleServer.serverInstance = new SimpleServer(admin);
				}
			}
		}
		return serverInstance;
	}

	protected HashMap<String, ServerEngine> getClientList() {
		return this.clientlist;
	}

	private void automateNotifications() {
		Thread thread = new Thread(new Runnable() {

			@Override
			public void run() {
				while (true) {
					SimpleServer.this
							.notifyObservers(SimpleServer.this.serverDataObject);
					try {
						Thread.sleep(1000);
					} catch (InterruptedException e) {
					}
				}
			}
		});
	}

	private void serverStateChanged() {
		this.setChanged();
//		this.set
		this.notifyObservers(this.serverDataObject);
	}

	protected void updateDataObject(String recentMessage, String[] bankAccounts, String[] personalAccounts) {
//		System.out.println("Updating AdministratorFrame");
		if (recentMessage != null) {
			this.serverDataObject.setRecentMessage(recentMessage);
		}
		
		if (bankAccounts != null) {
			this.serverDataObject.setBankAccounts(bankAccounts);
		}
		
		if (personalAccounts != null) {
			this.serverDataObject.setPersonalAccounts(personalAccounts);
		}
		
		if (recentMessage != null || bankAccounts != null || personalAccounts != null) {
//			System.out.println("trace");
			this.serverStateChanged();
			System.out.println(this.countObservers());
		}
	}
	
	// /**
	// * Returns the connection to the database driver
	// *
	// * @return connection used to the database
	// */
	// public Connection getSQLConnection() {
	// return this.sqlconnection.getConnection();
	// }
	
	public static void main(String[] args) throws IOException {
		SimpleServer ss = new SimpleServer();
	}

}
